﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace memcached
{
    /// <summary>
    /// Provide means for executing a function on a cache item in a synchronized manner.
    /// Thread safe, process-wide sync only.
    /// </summary>
    public sealed class TransMemCache : IDisposable
    {
        /// <summary>
        /// Use MemCache store client.
        /// </summary>
        [System.Diagnostics.DebuggerHidden]
        public TransMemCache() { }

        /// <summary>
        /// Initialize with provided client.
        /// </summary>
        /// <param name="c"></param>
        [System.Diagnostics.DebuggerHidden]
        public TransMemCache(ICacheAdvanced c)
        {
            if (c == null)
                throw new ArgumentNullException("c");
            client = c;
        }

        /// <summary>
        /// Get the value, perform operation and set the new value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="operation">function returning new value for item</param>
        /// <returns>false if Set failed</returns>
        public bool Perform<T>(string key, Func<T, T> operation)
        {
            if (operation == null)
                throw new ArgumentNullException("operation");
            if (client == null)
                Initialize();
            return Perform<T>(client, key, operation);
        }

        /// <summary>
        /// Get the value, perform operation and set the new value provided it was not changed in-between.
        /// This does not guarantee success to updateitem, yet one thread is guaranteed to succeed.
        /// </summary>
        /// <remarks>Thread safe</remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="operation">function returning new value for item</param>
        /// <returns>false if some other process updated the keyed item after we read it</returns>
        public bool PerformOnExisting<T>(string key, Func<T, T> operation)
        {
            if (operation == null)
                throw new ArgumentNullException("operation");
            if (client == null)
                Initialize();
            CasValue<T> res = client.Get<T>(key);
            if (res == null)
                throw new InvalidOperationException(string.Format("Key {0} does not exist in the cache.", key));
            T newValue = operation(res.Value);
            return client.Set(key, res.CAS, newValue);
        }

        /// <summary>
        /// Get value, perform operation, set new value and return operation status, all within a write-lock.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="c">store</param>
        /// <param name="key"></param>
        /// <param name="operation">to perform in process-wide write lock</param>
        /// <returns>false if Get or Set failed</returns>
        public static bool Perform<T>(ICache c, string key, Func<T, T> operation)
        {
            if (c == null)
                throw new ArgumentNullException("c");
            if (operation == null)
                throw new ArgumentNullException("operation");
            bool res;
            System.Threading.ReaderWriterLockSlim rw = SyncMemCache.GetLockFor(key);
            rw.EnterWriteLock();
            try
            {
                T value = c.Get<T>(key, out res);
                if (res)
                {
                    T newValue = operation(value);
                    res = c.Set(key, newValue);
                }
            }
            finally
            {
                rw.ExitWriteLock();
            }
            return res;
        }

        /// <summary>
        /// Get value and invoke operation within a process wide read lock.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="c">store</param>
        /// <param name="key"></param>
        /// <param name="operation">to perform in process-wide read lock</param>
        /// <returns></returns>
        public static bool Perform<T>(ICache c, string key, Action<T> operation)
        {
            if (c == null)
                throw new ArgumentNullException("c");
            if (operation == null)
                throw new ArgumentNullException("operation");
            bool res;
            System.Threading.ReaderWriterLockSlim rw = SyncMemCache.GetLockFor(key);
            rw.EnterReadLock();
            try
            {
                T value = c.Get<T>(key, out res);
                if (res)
                {
                    operation(value);
                }
            }
            finally
            {
                rw.ExitReadLock();
            }
            return res;
        }

        #region Housekeeping
        private ICacheAdvanced client;
        private bool owner;

        private void Initialize()
        {
            lock (this)
                if (client == null)
                {
                    owner = true;
                    client = new MemCache();
                }
        }

        /// <summary></summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            if (owner && client != null && client is IDisposable)
                lock (this)
                    if (client != null)
                    {
                        ((IDisposable)client).Dispose();
                        client = null;
                    }
        }
        #endregion
    }
}